﻿/* 
 * Copyright 2010 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using Ngilead.Core.Store;
using Ngilead.Core.Utils;
using Ngilead.Core.Store.Stateless;
using System.Collections;
using System.Reflection;
using Ngilead.Core.Exception;

namespace Ngilead.Core
{
    /// <summary>
    /// Persistent bean manager class
    /// </summary>
    public class PersistentBeanManager
    {
        /// <summary>
        /// The unique instance of the Persistence Bean Manager
        /// </summary>
        private static PersistentBeanManager _instance = null;

        /// <summary>
        /// Logger channel
        /// </summary>
        private static ILog logger = LogManager.GetLogger(typeof(PersistentBeanManager));

        private IProxyStore _proxyStore { get; set; }
        /// <summary>
        /// The associated Proxy informations store
        /// </summary>
        public IProxyStore ProxyStore
        {
            get
            {
                return _proxyStore;
            }
            set
            {
                logger.Info(String.Format("Using Proxy Store : {0}", value));
                _proxyStore = value;
                LazyKiller.ProxyStore = value;
            }
        }

        /// <summary>
        /// The POJO lazy killer
        /// </summary>
        protected LazyKiller LazyKiller { get; set; }

        /// <summary>
        /// Persistence utility
        /// </summary>
        protected IPersistenceUtil _persistenceUtil;

        /// <summary>
        /// The associated persistence util implementation
        /// </summary>
        public IPersistenceUtil PersistenceUtil
        {
            get
            {
                return _persistenceUtil;
            }
            set
            {
                logger.Info(String.Format("Using persistence util : {0}", value));
                _persistenceUtil = value;
                LazyKiller.PersistenceUtil = value;
            }
        }

        /// <summary>
        /// The pseudo unique instance of the singleton
        /// </summary>
        /// <returns>the unique instance of the singleton</returns>
        public static PersistentBeanManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new PersistentBeanManager();
                }
                return _instance;
            }
        }

        /// <summary>
        /// Get the pseudo unique instance of the singleton
        /// </summary>
        /// <returns>the unique instance of the singleton</returns>
        public static PersistentBeanManager GetInstance()
        {
            return Instance;
        }


        /// <summary>
        /// Empty Constructor
        /// </summary>
        public PersistentBeanManager()
        {
            LazyKiller = new LazyKiller();
            ProxyStore = new StatelessProxyStore();
        }

        /// <summary>
        /// Clone the NHibernate POJO
        /// </summary>
        /// <param name="obj">the object to store</param>
        /// <returns>the clone</returns>
        public T Clone<T>(T obj)
        {
            //	Precondition checking
            //
            if (obj == null)
            {
                return default(T);
            }
            if (PersistenceUtil == null)
            {
                throw new InvalidOperationException("No Persistence Util set !");
            }
            try
            {
                //	Flush any pending modifications before clone
                //
                PersistenceUtil.FlushIfNeeded();

                // Wrap the entity to clone to have a coherent collection and dictionary
                // management
                Wrapper clonedWrapper = (Wrapper)ClonePojo(new Wrapper(obj));
                return (T)clonedWrapper.Value;
            }
            finally
            {
                ProxyStore.CleanUp();
            }
        }

        /**
         * Merge the clone POJO to its Hibernate counterpart
         */
        public T Merge<T>(T obj)
        {
            // Precondition checking
            if (obj == null)
            {
                return default(T);
            }

            if (PersistenceUtil == null)
            {
                throw new InvalidOperationException("No Persistence Util set !");
            }

            //  Wrap the entity to merge to have a coherent collection and dictionary
            //  management
            //
            Wrapper mergedWrapper = (Wrapper)MergePojo(new Wrapper(obj));
            return (T)mergedWrapper.Value;
        }

        /*----------------------------------------------------------------------------------------*/
        #region Clone internal methods
        /// <summary>
        /// Clone the argument pojo
        /// </summary>
        /// <param name="pojo">Object</param>
        /// <returns>Clone</returns>
        protected Object ClonePojo(Object pojo)
        {
            //	Null checking
            //
            if (pojo == null)
            {
                return null;
            }

            //	Precondition checking : is the pojo managed by Hibernate
            //
            try
            {
                if (PersistenceUtil.IsPersistentPojo(pojo) == true)
                {
                    //	Proxy checking
                    //
                    if (PersistenceUtil.IsInitialized(pojo) == false)
                    {
                        //	If the root pojo is not initialized, replace it by null
                        //
                        return null;
                    }
                }
                else if (HoldPersistentObject(pojo) == false)
                {
                    //	Do not clone not persistent classes, since they do not necessary
                    //	implement Java Bean specification.
                    //
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info("ClonePojo(pojo) : third party instance, not cloned");
                    }
                    return pojo;
                }

                //	Clone the pojo
                //
                return LazyKiller.Detach(pojo);
            }
            finally
            {
                PersistenceUtil.CloseCurrentSession();
                ProxyStore.CleanUp();
            }
        }

        #endregion

        #region Merge internal methods
        /// <summary>
        /// Merge a simple pojo
        /// </summary>
        /// <param name="clonePojo"></param>
        /// <returns></returns>
        protected Object MergePojo(Object clonePojo)
        {
            //	Retrieve the NHibernate pojo to merge
            //
            try
            {
                Object id = null;
                try
                {
                    id = PersistenceUtil.GetId(clonePojo);
                    if (id == null)
                    {
                        if (logger.IsInfoEnabled)
                        {
                            logger.Info("MergePojo(clonePojo) : hibernate pojo not found, can be transient or deleted data");
                        }
                    }
                }
                catch (TransientObjectException)
                {
                    if (logger.IsInfoEnabled)
                    {
                        logger.Info("MergePojo(clonePojo) : transient object");
                    }
                }
                catch (NotPersistentObjectException)
                {
                    if (HoldPersistentObject(clonePojo) == false)
                    {
                        //	Do not merge not persistent instance, since they do not necessary
                        //	implement the Java bean specification
                        //
                        if (logger.IsInfoEnabled)
                        {
                            logger.Info("MergePojo(clonePojo) : third party object, not merged");
                        }
                        return clonePojo;
                    }
                    else
                    {
                        if (logger.IsInfoEnabled)
                        {
                            logger.Info("MergePojo(clonePojo) : wrapping object");
                        }
                    }
                }

                //	Merge the modification in a new NHibernate Pojo
                //
                return LazyKiller.Attach(clonePojo);
            }
            finally
            {
                PersistenceUtil.CloseCurrentSession();
                ProxyStore.CleanUp();
            }
        }

        #endregion

        #region Hold persistent object methods

        /// <summary>
        /// In deep persistent association checking.
        /// This method is used to detect wrapping object (ie not persistent
        /// class holding persistent associations)
        /// </summary>
        /// <param name="pojo">the wrapping pojo</param>
        /// <return>true if the pojo contains persistent member, false otherwise</return> 
        protected bool HoldPersistentObject(Object pojo)
        {
            return HoldPersistentObject(pojo, new ArrayList());
        }

        /// <summary>
        /// In deep persistent association checking.
        /// This method is used to detect wrapping object (ie not persistent
        /// class holding persistent associations)
        /// </summary>
        /// <param name="pojo">the wrapping pojo</param>
        /// <param name="alreadyChecked">list of already checked pojos</param>
        /// <return>true if the pojo contains persistent member, false otherwise</return> 
        protected bool HoldPersistentObject(Object pojo, ArrayList alreadyChecked)
        {
            //	Precondition checking
            //
            if ((pojo == null) ||
                (alreadyChecked.Contains(pojo)))
            {
                return false;
            }

            alreadyChecked.Add(pojo);
            Type type = pojo.GetType();

            //  Persistent class checking
            //
            if ((PersistenceUtil.IsEnhanced(type) == true) ||
                (PersistenceUtil.IsPersistentClass(type) == true) ||
                (PersistenceUtil.IsPersistentCollection(type) == true))
            {
                return true;
            }

            if (pojo is ValueType || pojo is String)
            {
                return false;
            }

            if (type.GetInterfaces().Contains(typeof(ICollection)))
            {
                ICollection collection = (ICollection)pojo;
                foreach (Object item in collection)
                {
                    if (HoldPersistentObject(item, alreadyChecked))
                    {
                        return true;
                    }
                }

                return false;
            }

            //	TODO Iterate over properties
            //
            PropertyInfo[] propertiesInfo = type.GetProperties();
            foreach (PropertyInfo propertyInfo in propertiesInfo)
            {
                Type propertyType = propertyInfo.GetType();

                //  Check value type
                //
                if (propertyType.IsValueType)
                {
                    continue;
                }

                // 	Not a basic type, so a check is needed
                //
                Object propertyValue = propertyInfo.GetValue(pojo, null);
                if (propertyValue == null)
                {
                    continue;
                }

                Type realPropertyType = propertyValue.GetType();
                if (PersistenceUtil.IsPersistentClass(realPropertyType) ||
                    PersistenceUtil.IsPersistentCollection(realPropertyType))
                {
                    return true;
                }

                //	Check property value
                //
                if (realPropertyType.GetInterfaces().Contains(typeof(IDictionary)))
                {
                    //	Check dictionary keys and values
                    //
                    IDictionary propertyDictionary = (IDictionary)propertyValue;
                    foreach (Object key in propertyDictionary.Keys)
                    {
                        if ((HoldPersistentObject(key, alreadyChecked) == true) ||
                            (HoldPersistentObject(propertyDictionary[key], alreadyChecked) == true))
                        {
                            return true;
                        }
                    }
                }
                else if (realPropertyType.GetInterfaces().Contains(typeof(ICollection)))
                {
                    // Check collection values
                    ICollection propertyCollection = (ICollection)propertyValue;
                    foreach (Object value in propertyCollection)
                    {
                        if (HoldPersistentObject(value, alreadyChecked) == true)
                        {
                            return true;
                        }
                    }
                }
                else if (realPropertyType.IsArray)
                {
                    //	Check array values
                    //
                    Object[] propertyArray = (Object[])propertyValue;
                    for (int i = 0; i < propertyArray.Length; i++)
                    {
                        if (HoldPersistentObject(propertyArray[i], alreadyChecked) == true)
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    //	Recursive search
                    //
                    if (HoldPersistentObject(propertyValue, alreadyChecked) == true)
                    {
                        return true;
                    }
                }
            }

            // No persistent property
            return false;

        }

        #endregion
    }
}
